Key Takeaway: Countries with higher GDP tend to have more positive migration rates, while those facing economic hardship or instability show higher emigration.
Understanding Global Migration Patterns
Where Are People Moving? ✈
Migration is a defining issue of our time, and its patterns vary widely across the world. Through this report, we explore how people are moving, how trends have changed over time, and how migration relates to economic and social indicators like GDP and life expectancy.
UNICEF data is used to tell this story through visualisations created in Python.
Code
import pandas as pd# Load the datasetsmigration = pd.read_csv('/content/unicef_indicator_2.csv')metadata = pd.read_csv('/content/unicef_metadata.csv')# Previewmigration.head()
Requirement already satisfied: plotnine in /usr/local/lib/python3.11/dist-packages (0.14.5)
Requirement already satisfied: geopandas in /usr/local/lib/python3.11/dist-packages (1.0.1)
Requirement already satisfied: matplotlib>=3.8.0 in /usr/local/lib/python3.11/dist-packages (from plotnine) (3.10.0)
Requirement already satisfied: pandas>=2.2.0 in /usr/local/lib/python3.11/dist-packages (from plotnine) (2.2.2)
Requirement already satisfied: mizani~=0.13.0 in /usr/local/lib/python3.11/dist-packages (from plotnine) (0.13.2)
Requirement already satisfied: numpy>=1.23.5 in /usr/local/lib/python3.11/dist-packages (from plotnine) (2.0.2)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.11/dist-packages (from plotnine) (1.14.1)
Requirement already satisfied: statsmodels>=0.14.0 in /usr/local/lib/python3.11/dist-packages (from plotnine) (0.14.4)
Requirement already satisfied: pyogrio>=0.7.2 in /usr/local/lib/python3.11/dist-packages (from geopandas) (0.10.0)
Requirement already satisfied: packaging in /usr/local/lib/python3.11/dist-packages (from geopandas) (24.2)
Requirement already satisfied: pyproj>=3.3.0 in /usr/local/lib/python3.11/dist-packages (from geopandas) (3.7.1)
Requirement already satisfied: shapely>=2.0.0 in /usr/local/lib/python3.11/dist-packages (from geopandas) (2.1.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (1.3.1)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (4.57.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (1.4.8)
Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (11.1.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (3.2.3)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->plotnine) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.11/dist-packages (from pandas>=2.2.0->plotnine) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.11/dist-packages (from pandas>=2.2.0->plotnine) (2025.2)
Requirement already satisfied: certifi in /usr/local/lib/python3.11/dist-packages (from pyogrio>=0.7.2->geopandas) (2025.1.31)
Requirement already satisfied: patsy>=0.5.6 in /usr/local/lib/python3.11/dist-packages (from statsmodels>=0.14.0->plotnine) (1.0.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil>=2.7->matplotlib>=3.8.0->plotnine) (1.17.0)
Code
from plotnine import*import geopandas as gpdimport matplotlib.pyplot as plt
Net Migration Rates by Country 🌎
Some countries, such as Saudi Arabia, attract high numbers of migrants, while others, such as Guyana, experience significant emigration. The map shows a snapshot of current movement patterns.
Code
# Read shapefileworld = gpd.read_file('/content/ne_110m_admin_0_countries.shp')# Fix known country name mismatchesname_fix = {'United States of America': 'United States','Russia': 'Russian Federation','Dem. Rep. Congo': 'Congo, the Democratic Republic of the','Republic of the Congo': 'Congo, Rep.','Viet Nam': 'Vietnam','Iran': 'Iran, Islamic Republic of','Syria': 'Syrian Arabic Republic','United Republic of Tanzania': 'Tanzania','Republic of Korea': 'South Korea','North Korea': "Korea, Democratic People's Republic of",'Laos': "Lao People's Democratic Republic",'Czechia': 'Czech Republic','Palestine': 'State of Palestine','Tanzania': 'Tanzania, United Republic of','Falkland Is.': 'Falkland Islands (Malvinas)','Bolivia': 'Bolivia, Plurinational State of','Venezuela': 'Venezuela, Bolivarian Republic of',"Côte d'Ivoire": 'Ivory Coast','Central African Rep.': 'Central African Republic','Moldova': 'Moldova, Republic of','Libya': 'Libyan Arab Jamahiriya','Bosnia and Herz.': 'Bosnia and Herzegovina','North Macedonia': 'Macedonia, the former Yugoslav Republic of','S. Sudan': 'South Sudan',}world['country_fixed'] = world['NAME'].replace(name_fix)# Prepare migration dataavg_migration = migration.groupby('country', as_index=False)['obs_value'].mean()# Merge using fixed namesmap_df = world.merge(avg_migration, how='left', left_on='country_fixed', right_on='country')# Plot the mapmap_df.plot(column='obs_value', cmap='RdYlGn', legend=True, figsize=(15, 8), edgecolor='black', vmin=-10, vmax=10)plt.title('Average Net Migration Rate by Country')plt.axis('off')plt.show()
Global Net Migration Over Time ⏳
Over the past few decades, global migration has shifted significantly. Notice the peaks and dips - what historical events might have driven these trends? Wars, economic crises, and policy changes all shape global migration flows!
Some of the most noticeable shifts in migration patterns occurred during the following periods:
1960s, as more and more colonies gained independence
1991, the collapse of Soviet Union
2000s, due to the expansion of the European Union
Code
global_migration = migration.groupby('time_period', as_index=False)['obs_value'].mean()ggplot(global_migration, aes('time_period', 'obs_value')) +\ geom_line(color='#0077b6') +\ labs(title='Global Average Net Migration Over Time', x='Year', y='Average Net Migration Rate') +\ theme_minimal()
Migration Rate by Continent 🧭
Migration isn’t just about countries - it’s about entire regions. This bar chart shows which continents see more people arriving versus leaving. Europe and Asia generally have higher positive migration rates, while Africa and the Americas often see negative migration rates. Currently, Oceania has the lowest net migration rate in the world. These patterns reflect economic opportunities, political stability, and demographic trends.
from pandas.api.types import CategoricalDtypefrom plotnine import*# Step 1: Group and sort the dataavg_by_continent = migration.groupby('continent', as_index=False)['obs_value'].mean()avg_by_continent = avg_by_continent.sort_values('obs_value', ascending=False)# Step 2: Create a custom column for color (green for positive, red for negative)avg_by_continent['bar_color'] = avg_by_continent['obs_value'].apply(lambda x: 'green'if x >=0else'red')# Step 3: Reorder 'continent' for descending bar ordercontinent_order = avg_by_continent['continent'].tolist()cat_type = CategoricalDtype(categories=continent_order, ordered=True)avg_by_continent['continent'] = avg_by_continent['continent'].astype(cat_type)# Step 4: Plot with color appliedggplot(avg_by_continent, aes(x='continent', y='obs_value', fill='bar_color')) +\ geom_bar(stat='identity') +\ scale_fill_manual(values={'green': '#2ca02c', 'red': '#d62728'}) +\ labs(title='Average Net Migration Rate by Continent', x='Continent', y='Net Migration Rate') +\ theme_minimal() +\ theme(legend_position='none')
Net Migration vs GDP per Capita 💰
This scatterplot suggests a general trend: richer countries tend to attract more migrants, while poorer countries often experience emigration.
Economic opportunity is a major driver of migration, but it isn’t the only factor. Some wealthy nations still see people leaving, while some poorer nations attract migrants due to other factors such as military stability or social policies.
This chart is interactive, hover over the points to explore different countries and regions.
Code
!pip install plotly
Requirement already satisfied: plotly in /usr/local/lib/python3.11/dist-packages (5.24.1)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.11/dist-packages (from plotly) (9.1.2)
Requirement already satisfied: packaging in /usr/local/lib/python3.11/dist-packages (from plotly) (24.2)
Code
import plotly.express as pximport plotly.graph_objects as goimport numpy as np# Prepare the data againavg_mig = migration.groupby('country', as_index=False)['obs_value'].mean()avg_gdp = metadata.groupby('country', as_index=False)['GDP per capita (constant 2015 US$)'].mean()merged = avg_mig.merge(avg_gdp, on='country')# Add continentmerged['continent'] = merged['country'].map(continent_map)# Drop rows with missing values in either columnmerged = merged.dropna(subset=['obs_value', 'GDP per capita (constant 2015 US$)'])# Get X and Y for regressionx = merged['GDP per capita (constant 2015 US$)']y = merged['obs_value']# Calculate linear regression lineslope, intercept = np.polyfit(x, y, 1)regression_line = slope * x + intercept# Create base scatterplotfig = px.scatter( merged, x='GDP per capita (constant 2015 US$)', y='obs_value', color='continent', hover_name='country', title='Interactive: Net Migration Rate vs GDP per Capita (with Regression Line)', labels={'obs_value': 'Net Migration Rate','GDP per capita (constant 2015 US$)': 'Average GDP per Capita (USD)' })# Add regression line manuallyfig.add_trace( go.Scatter( x=x, y=regression_line, mode='lines', name='Linear Regression', line=dict(color='black', dash='dash') ))# Show itfig.show()
To Conclude…
Migration is shaped by a complex web of factors such as economic opportunity or political stability. This page highlights some key migration trends, and their connection to wealth and geography. While some migration is voluntary - seeking better jobs, education, or quality of life - other migration is forced, driven by war, climate change, and persecution.